/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Engine.

    Maxsi Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiEngineContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiEngineContributors.h. For more information please see
	MaxsiEngineContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiEngine.dll
	A linkable version of Maxsi Engine

	MaxsiBuffer.cpp
	A flexible buffer class.

****************************************************************************/

#include "MaxsiEngine.h"


namespace MaxsiEngine
{
	
	//=============================================================================
	//	class ME_Buffer
	//=============================================================================

	ME_Buffer::ME_Buffer()
	{
		Buffer			=	NULL;
		BufferSize		=	0;
		BufferUsed		=	0;
		BufferOffset	=	0;
		BufferIdealSize	=	BufferSize;
		m_BufferFlags	=	0;		

		ShouldDoEvenSillierDebugStuff		=	false;
		
		if (!InitializeCriticalSectionAndSpinCount(&BufferLock, 0x80000400) )
		{	
			ME_UNDEFINED_BEHAVIOR("ME_Buffer::ME_Buffer() couldn't initialize InitializeCriticalSectionAndSpinCount(&BufferLock)!");
		}
	}

	ME_Buffer::~ME_Buffer()
	{
		if ( Buffer != NULL ) { delete[] Buffer; }

		DeleteCriticalSection(&BufferLock);
	}

	//=============================================================================
	//	class ME_Buffer - Functions
	//=============================================================================
	
	//=============================================================================
	//	int ME_Buffer::MarkDataAsRead(char* Pointer, size_t Size)
	//
	//	Called when data in the buffer is no longer used. This function resets
	//	the data to the default value when in secure mode so that personal data
	//	exists in the memory as little time as possible.
	//=============================================================================
	
	int		ME_Buffer::MarkDataAsRead(char* Pointer, size_t Size)
	{
		if	( GetBufferFlags() & ME_Buffer_Flag_Secure )
		{
			memset(Pointer,0xCC,Size);
		}
		return	ME_ERROR_SUCCESS;
	}

	//=============================================================================
	//	int ME_Buffer::Unlock()
	//
	//	Unlocks the buffer so that other threads can use it and revokes all rights
	//	to the buffer from thr thread.
	//=============================================================================

	int		ME_Buffer::Unlock()
	{
		SetBufferFlags(GetBufferFlags()	& ~ME_Buffer_Flag_AllRights);
		
		if ( BufferLock.RecursionCount == 0 )
		{
			// Waaaaiit a minute! We're not even locked. Did a programmer accidentally
			// unlock the wrong buffer?
			ME_DONT_COMPILE_RELEASE_BUILD(); // If this proves useful, consider leaving this warning so this doesn't happen again.
			ME_UNDEFINED_BEHAVIOR("Attempted to unlock an unlocked buffer!");
		}

		
#ifdef _DEBUG
		if ( BufferLock.RecursionCount == 1 )
		{
			DebugCaller		=	NULL;
			DebugLine		=	NULL;
		}
#endif

		LeaveCriticalSection(&BufferLock);

		return	ME_ERROR_SUCCESS;
	}

	//=============================================================================
	//	int ME_Buffer::Lock(int Flags)
	//
	//	Locks the buffer so that only the current thread can acceess it with the
	//	rights specified in the Flags parameter.
	//=============================================================================

	int		ME_Buffer::Lock(int Flags
#ifdef _DEBUG
	, char*		Caller
	, size_t	Line
#endif
	)
	{
		EnterCriticalSection(&BufferLock);

#ifdef _DEBUG
		if ( DebugCaller == NULL && DebugLine == NULL )
		{
			DebugCaller		=	Caller;
			DebugLine		=	Line;
		}
#endif

		SetBufferFlags(GetBufferFlags()	|	Flags);

		return	ME_ERROR_SUCCESS;
	}

	//=============================================================================
	//	int ME_Buffer::Write(char* Input, size_t Size)
	//
	//	Writes data to the buffer if the thread has the correct rights.
	//=============================================================================

	int		ME_Buffer::Write(char* Input, size_t Size)
	{
		int	Result	=	ME_ERROR_SUCCESS;

		EnterCriticalSection(&BufferLock);
		
		// See if we may write to the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Write) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else
		{
			// Do we have a buffer?
			if ( Buffer == NULL )
			{
				Result	=	ME_ERROR_NOT_INITIALIZED;
			}
			else
			{
				// Is there room in the buffer?
				if ( BufferSize - BufferUsed < Size )
				{
					// Nope.
					Result			=	ME_ERROR_OUTOFMEM;
				}
				else
				{
					// Yes, write to the buffer right away. Calculate where to size.

					size_t	Begin	=	(BufferOffset+BufferUsed) % BufferSize;

					if ( Begin+Size > BufferSize )
					{
						memcpy(Buffer+Begin,Input,BufferSize-Begin);
						memcpy(Buffer,Input+BufferSize-Begin,Size-(BufferSize-Begin));
					}
					else
					{
						memcpy(Buffer+Begin,Input,Size);
					}

					SetBufferUsed(BufferUsed+Size);
				}
			}
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	int ME_Buffer::Write(ME_Buffer* Input, size_t Size)
	//
	//	Writes data to the buffer if the thread has the correct rights.
	//=============================================================================

	int		ME_Buffer::Write(ME_Buffer* Input, size_t Size)
	{
		ME_TODO("Implement this!");
		return 0;
	}

	//=============================================================================
	//	int ME_Buffer::Read(char* Dest, size_t Size);
	//
	//	Reads data from the buffer if the thread has the correct rights.
	//=============================================================================

	int		ME_Buffer::Read(char* Dest, size_t Size)
	{
		int	Result	=	ME_ERROR_SUCCESS;

		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Read) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else
		{
			// Do we have a buffer?
			if ( Buffer == NULL )
			{
				Result	=	ME_ERROR_NOT_INITIALIZED;
			}
			else
			{
				// Is there room in the buffer?
				if ( BufferUsed < Size )
				{
					// Nope.
					Result			=	ME_ERROR_OUTOFMEM;
				}
				else
				{
					// Yes, read data from the buffer right away. Calculate where to size.

					if ( BufferOffset+Size > BufferSize )
					{
						memcpy(Dest,Buffer+BufferOffset,BufferSize-BufferOffset);
						memcpy(Dest+BufferSize-BufferOffset,Buffer,Size-(BufferSize-BufferOffset));					

						MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
						MarkDataAsRead(Buffer,Size-(BufferSize-BufferOffset));
					}
					else
					{
						memcpy(Dest,Buffer+BufferOffset,Size);
						MarkDataAsRead(Buffer+BufferOffset,Size);
					}

					BufferOffset	+=	Size;
					BufferUsed		-=	Size;					
					BufferOffset	%=	BufferSize;

					if ( BufferSize != BufferIdealSize )
					{
						SetBufferSize(BufferIdealSize);
					}
				}
			}
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	
	//=============================================================================
	//	char* ME_Buffer::GetBuffer()
	//
	//	Returns a pointer to the buffer if the thread has the correct rights,
	//	NULL otherwise.
	//=============================================================================

	char*	ME_Buffer::GetBuffer()
	{
		char*	Result	=	NULL;
		EnterCriticalSection(&BufferLock);
		if ( GetBufferFlags() & ME_Buffer_Flag_Pointer )
		{
			Result		=	Buffer;
		}
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	char* ME_Buffer::GetPointer()
	//
	//	Returns a pointer to the current byte in the buffer if the thread has the
	//	correct rights, NULL otherwise.
	//=============================================================================

	char*	ME_Buffer::GetPointer()
	{
		char*	Result	=	NULL;
		EnterCriticalSection(&BufferLock);
		if ( GetBufferFlags() & ME_Buffer_Flag_Pointer )
		{
			Result		=	Buffer+BufferOffset;
		}
		else
		{
			__debugbreak(); // This shouldn't happen unless the program was programmed
							// poorly, or if there is a bug.
		}
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	size_t ME_Buffer::GetBufferSize()
	//
	//	Returns the current buffer size.
	//=============================================================================

	size_t	ME_Buffer::GetBufferSize()
	{
		return	BufferSize;
	}

	//=============================================================================
	//	size_t ME_Buffer::GetBufferOffset()
	//
	//	Returns the buffer currently used.
	//=============================================================================

	size_t	ME_Buffer::GetBufferUsed()
	{
		return	BufferUsed;
	}

	//=============================================================================
	//	size_t ME_Buffer::GetBufferOffset()
	//
	//	Returns the current buffer offset.
	//=============================================================================

	size_t	ME_Buffer::GetBufferOffset()
	{
		return	BufferOffset;
	}

	//=============================================================================
	//	size_t ME_Buffer::GetBufferAvailable()
	//
	//	Returns the amount of unused space in this buffer
	//=============================================================================

	size_t	ME_Buffer::GetBufferAvailable()
	{
		return	BufferSize-BufferUsed;
	}


	//=============================================================================
	//	size_t ME_Buffer::GetPointerSizeFollowingData()
	//
	//	Returns the amount of bytes in a row following the current position in the
	//	buffer.
	//=============================================================================

	size_t	ME_Buffer::GetPointerSizeFollowingData()
	{
		if ( BufferSize-BufferOffset > BufferUsed ) { return BufferUsed; }
		else return BufferSize-BufferOffset;
		//return	min(BufferSize-BufferOffset,BufferUsed);
	}

	//=============================================================================
	//	int ME_Buffer::SetBufferSize(size_t Size, bool Force)
	//
	//	Changes the size of the buffer if possible, and fails if not possible,
	//	unless the Force parameter is set. If the size change is failed, it is
	//	requested and will be done as soon as the buffer is small enough. If the
	//	Force Parameter is set, the contents of the buffer after the operation
	//	is undefined.
	//=============================================================================

	int		ME_Buffer::SetBufferSize(size_t Size, bool Force)
	{
		int	Result	=	ME_ERROR_SUCCESS;

		EnterCriticalSection(&BufferLock);

		if ( (Buffer && GetBufferFlags() & ME_Buffer_Flag_Pointer) || !(GetBufferFlags() & ME_Buffer_Flag_Write) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
			BufferIdealSize	=	Size;
		}
		else
		{
			if ( Buffer == NULL )
			{
				Buffer		=	new	char[Size];
				if ( Buffer == NULL )
				{
					Result			=	ME_ERROR_MEMALLOCFAIL;
					BufferIdealSize	=	Size;
				}
				else
				{
					BufferUsed		=	0;
					BufferOffset	=	0;
					BufferSize		=	Size;
					BufferIdealSize	=	BufferSize;
				}
			}
			else
			{
				if ( BufferUsed > Size && Force == false )
				{
					Result			=	ME_ERROR_OUTOFMEM;
					BufferIdealSize	=	Size;
				}
				else
				{
					char*	NewBuffer	=	new	char[Size];
					if ( NewBuffer == NULL )
					{
						Result			=	ME_ERROR_MEMALLOCFAIL;
						BufferIdealSize	=	Size;
					}
					else
					{
						if ( BufferUsed > Size )
						{
							// Warning: We changed the size of the buffer to something smaller and forced it
							// to be done right away, leaving the contents of the new buffer undefined.
						}
						else
						{
							if ( BufferOffset+BufferUsed > BufferSize )
							{
								memcpy(NewBuffer,Buffer+BufferOffset,BufferSize-BufferOffset);
								memcpy(NewBuffer+BufferSize-BufferOffset,Buffer,BufferUsed-(BufferSize-BufferOffset));
							}
							else
							{
								memcpy(NewBuffer,Buffer,BufferUsed);
							}
							MarkDataAsRead(Buffer,BufferSize);
							delete[]	Buffer;
							Buffer			=	NewBuffer;
							BufferOffset	=	0;
							BufferSize		=	Size;
							BufferIdealSize	=	BufferSize;
						}
					}
				}

			}
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	int ME_Buffer::SetBufferOffset(size_t Offset)
	//
	//	Changes the buffer offset if the thread has the correct access rights.
	//=============================================================================

	int		ME_Buffer::SetBufferOffset(size_t Offset)
	{
		int	Result	=	ME_ERROR_SUCCESS;

		EnterCriticalSection(&BufferLock);

		if ( GetBufferFlags() & ME_Buffer_Flag_Pointer && GetBufferFlags() & ME_Buffer_Flag_Write )
		{
			BufferOffset	=	Offset;
		}
		else
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	int ME_Buffer::SetBufferUsed(size_t Used)
	//
	//	Changes the buffer used if the thread has the correct access rights.
	//=============================================================================

	int		ME_Buffer::SetBufferUsed(size_t Used)
	{
		int	Result	=	ME_ERROR_SUCCESS;

		EnterCriticalSection(&BufferLock);

		if ( GetBufferFlags() & ME_Buffer_Flag_Pointer && GetBufferFlags() & ME_Buffer_Flag_Write )
		{
			if ( Used > BufferSize )
			{
				ME_UNDEFINED_BEHAVIOR("Attempted to set buffer usage higher than the buffer's size! This is most likely caused by a failure in the program's logic.");
			}
			BufferUsed		=	Used;
		}
		else
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	char* ME_Buffer::ProgressPointer(char* Position, size_t Size)
	//
	//	Returns the same pointer moved Size bytes further into the buffer.
	//	Returns NULL if the thread doesn't have pointer access, or if the pointer
	//	provided is not in the buffer
	//=============================================================================

	char*	ME_Buffer::ProgressPointer(char* Position, size_t Size)
	{
		char*	Result	=	NULL;

		EnterCriticalSection(&BufferLock);

		if ( GetBufferFlags() & ME_Buffer_Flag_Pointer &&
			(size_t) Position >= (size_t)Buffer &&
			(size_t) Position <= (size_t)Buffer+BufferSize) 
		{
			Result	=	Buffer+((((size_t)Position-(size_t)Buffer)+Size)%BufferSize);
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	int ME_Buffer::ProgressBuffer(size_t Size)
	//
	//	Progresses the buffer Size bytes forward and returns an error if it goes
	//	beyond the size of the buffer. If the Force parameter is set the buffer is
	//	progressed no matter what, but the contents are undefined.
	//=============================================================================
	int		ME_Buffer::ProgressBuffer(size_t Size, bool Force)
	{
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Read) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else
		{
			if ( BufferUsed < Size )
			{
				if ( Force == false )
				{
					Result	=	ME_ERROR_OUTOFMEM;
				}
				else
				{
					// Contents are undefined.
					BufferUsed		=	0;
					BufferOffset	+=	Size;
					BufferOffset	%=	BufferSize;
				}
			}
			else
			{	
				if ( BufferOffset+Size > BufferSize )
				{	
					MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
					MarkDataAsRead(Buffer,Size-(BufferSize-BufferOffset));
				}
				else
				{
					MarkDataAsRead(Buffer+BufferOffset,Size);
				}

				BufferUsed		-=	Size;
				BufferOffset	+=	Size;
				BufferOffset	%=	BufferSize;
			}
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	
	//=============================================================================
	//	int ME_Buffer::Clear()
	//
	//	Marks all data in the buffer as read and resets the pointer to the first
	//	byte in the buffer.
	//=============================================================================
	int		ME_Buffer::Clear()
	{
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Read) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else
		{
			if ( BufferOffset+BufferUsed > BufferSize )
			{	
				MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
				MarkDataAsRead(Buffer,BufferUsed-(BufferSize-BufferOffset));
			}
			else
			{
				MarkDataAsRead(Buffer+BufferOffset,BufferUsed);
			}

			BufferUsed		=	0;
			BufferOffset	=	0;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	//=============================================================================
	//	int ME_Buffer::Clear()
	//
	//	Marks all data in the buffer as read and resets the pointer to the first
	//	byte in the buffer.
	//=============================================================================

	int		ME_Buffer::SetInUse()
	{
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		//if ( !(GetBufferFlags() & ME_Buffer_Flag_Pointer) )
		//{
		//	Result			=	ME_ERROR_ACCESS_VIOLATION;
		//}
		//else
		//{
			SetBufferFlags(GetBufferFlags()		|	ME_Buffer_Flag_InUse);
		//}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}
	
	int		ME_Buffer::ClearInUse()
	{
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		//// See if we may read from the buffer
		//if ( !(GetBufferFlags() & ME_Buffer_Flag_Pointer) )
		//{
		//	Result			=	ME_ERROR_ACCESS_VIOLATION;
		//}
		//else
		//{
			SetBufferFlags(GetBufferFlags()	&	~ME_Buffer_Flag_InUse);
		//}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	
	int		ME_Buffer::GetBufferFlags()
	{
		return	m_BufferFlags;
	}
	int		ME_Buffer::SetBufferFlags(int NewFlags)
	{
		m_BufferFlags	=	NewFlags;
		return	NewFlags;
	}

	bool	ME_Buffer::IsInUse()
	{	
		bool	Result	=	false;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( GetBufferFlags() & ME_Buffer_Flag_InUse )
		{
			Result			=	true;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}

	int		ME_Buffer::MayWrite()
	{		
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Write) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else if ( GetBufferFlags() & ME_Buffer_Flag_InUse )
		{
			Result			=	ME_ERROR_IS_IN_USE;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}
	int		ME_Buffer::MayRead()
	{		
		int	Result	=	ME_ERROR_SUCCESS;
		
		EnterCriticalSection(&BufferLock);
		
		// See if we may read from the buffer
		if ( !(GetBufferFlags() & ME_Buffer_Flag_Read) )
		{
			Result			=	ME_ERROR_ACCESS_VIOLATION;
		}
		else if ( GetBufferFlags() & ME_Buffer_Flag_InUse )
		{
			Result			=	ME_ERROR_IS_IN_USE;
		}
		
		LeaveCriticalSection(&BufferLock);

		return	Result;
	}


	ME_Buffer*		CreateBuffer(size_t Size, int flags)
	{
		ME_Buffer*	Result	=	new	ME_Buffer;
		if ( Result == NULL ) { return NULL; }
		
		Result->Lock(ME_Buffer_Flag_AllRights | flags ME_BUFFER_LOCK_PARAMSEX);
		Result->SetBufferSize(Size);
		Result->Unlock();

		return	Result;
	}

	bool			DeleteBuffer(ME_Buffer* Buffer)
	{
		delete	Buffer;
		return	true;
	}


	size_t			FindStringInBuffer(ME_Buffer* Buffer, char* String2)
	{
		return FindStringInBufferEx(Buffer,String2,strlen(String2));
	}

	size_t			FindStringInBufferEx(ME_Buffer* Buffer, char* String2, size_t String2Len)
	{
		if ( Buffer->MayRead() !=	ME_ERROR_SUCCESS )
		{
			return SIZE_MAX;
		}

		char*	Pointer		=	Buffer->GetBuffer();

		if ( Pointer ==	NULL )
		{
			return SIZE_MAX;
		}

		size_t	Row		=	Buffer->GetPointerSizeFollowingData();
		size_t	Used	=	Buffer->GetBufferUsed();
		size_t	Size	=	Buffer->GetBufferSize();
		size_t	Offset	=	Buffer->GetBufferOffset();

		if ( Row >= Used )
		{
			return	FindStringEx(Pointer+Offset,Used,String2,String2Len);
		}
		else
		{			

			// This is a bit more tricky since the buffer wraps

			size_t X = 0, Y = 0;

			Used	-=	String2Len;

			// Loop through X and check at each X if Y follows, if so return X
			for (X = 0; X < Used; X++)
			{
				for (Y = 0; Y < String2Len; Y++)
				{
					if ( Pointer[(Offset+Y)%Size] != String2[Y] )
					{
						break; // Nope.
					}
				}
				if ( Y == String2Len )
				{
					return X; // Found at location X
				}
			}
		}		
		
		return SIZE_MAX; // Not Found!
	}

}
